﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Hashtable=System.Collections.Hashtable;
using ICollection=System.Collections.ICollection;

namespace OpenReflect.Services
{
	#region mono linker license
	//
	// AssemblyResolver.cs
	//
	// Author:
	//   Jb Evain (jbevain@novell.com)
	//
	// (C) 2007 Novell, Inc.
	//
	// Permission is hereby granted, free of charge, to any person obtaining
	// a copy of this software and associated documentation files (the
	// "Software"), to deal in the Software without restriction, including
	// without limitation the rights to use, copy, modify, merge, publish,
	// distribute, sublicense, and/or sell copies of the Software, and to
	// permit persons to whom the Software is furnished to do so, subject to
	// the following conditions:
	//
	// The above copyright notice and this permission notice shall be
	// included in all copies or substantial portions of the Software.
	//
	// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
	// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
	// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
	// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
	// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
	//
	#endregion

	//This code piece is taken from the Mono Linker Project,
	//which can be seen at http://anonsvn.mono-project.com/viewvc/trunk/mcs/tools/linker/Mono.Linker/AssemblyResolver.cs?content-type=text%2Fplain&view=co
	public class AssemblyResolver:BaseAssemblyResolver
	{
		IDictionary<string,AssemblyDefinition> assemblies;

		public IDictionary<string,AssemblyDefinition> AssemblyCache
		{
			get { return assemblies; }
		}

		public AssemblyResolver()
		{
			assemblies = new Dictionary<string, AssemblyDefinition>();
		}

		public override AssemblyDefinition Resolve(AssemblyNameReference name)
		{
			AssemblyDefinition asm;
			assemblies.TryGetValue(name.Name, out asm);
			if (asm == null)
			{
				asm = base.Resolve(name);
				asm.Resolver = this;
				assemblies[name.Name] = asm;
			}

			return asm;
		}

		public TypeDefinition Resolve(TypeReference type)
		{
			type = type.GetOriginalType();

			if (type is TypeDefinition)
				return (TypeDefinition)type;

			AssemblyNameReference reference = type.Scope as AssemblyNameReference;
			if (reference != null)
			{
				AssemblyDefinition assembly = Resolve(reference);
				return assembly.MainModule.Types[type.FullName];
			}

			ModuleDefinition module = type.Scope as ModuleDefinition;
			if (module != null)
				return module.Types[type.FullName];

			throw new NotImplementedException();
		}

		public FieldDefinition Resolve(FieldReference field)
		{
			TypeDefinition type = Resolve(field.DeclaringType);
			return GetField(type.Fields, field);
		}

		static FieldDefinition GetField(ICollection collection, FieldReference reference)
		{
			foreach (FieldDefinition field in collection)
			{
				if (field.Name != reference.Name)
					continue;

				if (!AreSame(field.FieldType, reference.FieldType))
					continue;

				return field;
			}

			return null;
		}

		public MethodDefinition Resolve(MethodReference method)
		{
			TypeDefinition type = Resolve(method.DeclaringType);
			method = method.GetOriginalMethod();
			if (method.Name == MethodDefinition.Cctor || method.Name == MethodDefinition.Ctor)
				return GetMethod(type.Constructors, method);
			else
				return GetMethod(type, method);
		}

		MethodDefinition GetMethod(TypeDefinition type, MethodReference reference)
		{
			while (type != null)
			{
				MethodDefinition method = GetMethod(type.Methods, reference);
				if (method == null)
					type = Resolve(type.BaseType);
				else
					return method;
			}

			return null;
		}

		static MethodDefinition GetMethod(System.Collections.ICollection collection, MethodReference reference)
		{
			foreach (MethodDefinition meth in collection)
			{

				if (meth.Name != reference.Name)
					continue;

				if (!AreSame(meth.ReturnType.ReturnType, reference.ReturnType.ReturnType))
					continue;

				if (!AreSame(meth.Parameters, reference.Parameters))
					continue;

				return meth;
			}

			return null;
		}

		static bool AreSame(ParameterDefinitionCollection a, ParameterDefinitionCollection b)
		{
			if (a.Count != b.Count)
				return false;

			if (a.Count == 0)
				return true;

			for (int i = 0; i < a.Count; i++)
				if (!AreSame(a[i].ParameterType, b[i].ParameterType))
					return false;

			return true;
		}

		static bool AreSame(ModType a, ModType b)
		{
			if (!AreSame(a.ModifierType, b.ModifierType))
				return false;

			return AreSame(a.ElementType, b.ElementType);
		}

		static bool AreSame(TypeSpecification a, TypeSpecification b)
		{
			if (a is GenericInstanceType)
				return AreSame((GenericInstanceType)a, (GenericInstanceType)b);

			if (a is ModType)
				return AreSame((ModType)a, (ModType)b);

			return AreSame(a.ElementType, b.ElementType);
		}

		static bool AreSame(GenericInstanceType a, GenericInstanceType b)
		{
			if (!AreSame(a.ElementType, b.ElementType))
				return false;

			if (a.GenericArguments.Count != b.GenericArguments.Count)
				return false;

			if (a.GenericArguments.Count == 0)
				return true;

			for (int i = 0; i < a.GenericArguments.Count; i++)
				if (!AreSame(a.GenericArguments[i], b.GenericArguments[i]))
					return false;

			return true;
		}

		static bool AreSame(GenericParameter a, GenericParameter b)
		{
			return a.Position == b.Position;
		}

		static bool AreSame(TypeReference a, TypeReference b)
		{
			if (a is TypeSpecification || b is TypeSpecification)
			{
				if (a.GetType() != b.GetType())
					return false;

				return AreSame((TypeSpecification)a, (TypeSpecification)b);
			}

			if (a is GenericParameter || b is GenericParameter)
			{
				if (a.GetType() != b.GetType())
					return false;

				return AreSame((GenericParameter)a, (GenericParameter)b);
			}

			return a.FullName == b.FullName;
		}

		public void CacheAssembly(AssemblyDefinition assembly)
		{
			assemblies[assembly.Name.Name] = assembly;
			assembly.Resolver = this;
		}

	}
}
